﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
#if NETFX_CORE
using Windows.Storage.Streams;
#endif

namespace Microsoft.Media.ISO
{
    public static class MP4BoxWriter
    {
        const uint BASEBOXSIZE = 8;
        const uint LARGESIZEADD = 8;

        static IDictionary<Guid, Action<IUniversalUniqueIdentifierBox, BoxBinaryWriter>> extendedBoxWriters;
        static IDictionary<string, Action<IBox, BoxBinaryWriter>> boxWriters;

        static MP4BoxWriter()
        {
            extendedBoxWriters = new Dictionary<Guid, Action<IUniversalUniqueIdentifierBox, BoxBinaryWriter>>();
            extendedBoxWriters.Add(new Guid("6d1d9b05-42d5-44e6-80e2-141daff757b2"), (b, w) => WriteTrackFragmentExtendedHeaderBox((TrackFragmentExtendedHeaderBox)b, w));
            extendedBoxWriters.Add(new Guid("a5d40b30-e814-11dd-ba2f-0800200c9a66"), (b, w) => WriteLiveServerManifestBox((LiveServerManifestBox)b, w));

            boxWriters = new Dictionary<string, Action<IBox, BoxBinaryWriter>>();
            boxWriters.Add("ftyp", (b, w) => WriteFileTypeBox((FileTypeBox)b, w));
            boxWriters.Add("uuid", (b, w) => WriteUniversalUniqueIdentifierBox((IUniversalUniqueIdentifierBox)b, w));
            boxWriters.Add("moov", (b, w) => WriteMovieBox((MovieBox)b, w));
            boxWriters.Add("mvhd", (b, w) => WriteMovieHeaderBox((MovieHeaderBox)b, w));
            boxWriters.Add("trak", (b, w) => WriteTrackBox((TrackBox)b, w));
            boxWriters.Add("mdia", (b, w) => WriteMediaBox((MediaBox)b, w));
            boxWriters.Add("tkhd", (b, w) => WriteTrackHeaderBox((TrackHeaderBox)b, w));
            boxWriters.Add("mdhd", (b, w) => WriteMediaHeaderBox((MediaHeaderBox)b, w));
            boxWriters.Add("hdlr", (b, w) => WriteHandlerReferenceBox((HandlerReferenceBox)b, w));
            boxWriters.Add("minf", (b, w) => WriteMediaInformationBox((MediaInformationBox)b, w));
            boxWriters.Add("stss", (b, w) => WriteSyncSamplesBox((SyncSamplesBox)b, w));
            boxWriters.Add("vmhd", (b, w) => WriteVideoMediaHeaderBox((VideoMediaHeaderBox)b, w));
            boxWriters.Add("smhd", (b, w) => WriteSoundMediaHeaderBox((SoundMediaHeaderBox)b, w));
            boxWriters.Add("dinf", (b, w) => WriteDataInformationBox((DataInformationBox)b, w));
            boxWriters.Add("dref", (b, w) => WriteDataReferenceBox((DataReferenceBox)b, w));
            boxWriters.Add("nmhd", (b, w) => WriteNullMediaHeaderBox((NullMediaHeaderBox)b, w));
            boxWriters.Add("udta", (b, w) => WriteUserDataBox((UserDataBox)b, w));
            boxWriters.Add("meta", (b, w) => WriteMetaBox((MetaBox)b, w));
            boxWriters.Add("ilst", (b, w) => WriteItemListBox((ItemListBox)b, w));
            boxWriters.Add("Xtra", (b, w) => WriteXtraBox((XtraBox)b, w));
            boxWriters.Add("url ", (b, w) => WriteUrlBox((UrlBox)b, w));
            boxWriters.Add("stbl", (b, w) => WriteSampleTableBox((SampleTableBox)b, w));
            boxWriters.Add("stts", (b, w) => WriteDecodingTimeToSampleBox((DecodingTimeToSampleBox)b, w));
            boxWriters.Add("ctts", (b, w) => WriteCompositionTimeToSampleBox((CompositionTimeToSampleBox)b, w));
            boxWriters.Add("stsc", (b, w) => WriteSampleChunkBox((SampleChunkBox)b, w));
            boxWriters.Add("stco", (b, w) => WriteSampleChunkOffsetBox((SampleChunkOffsetBox)b, w));
            boxWriters.Add("stsz", (b, w) => WriteSampleSizeBox((SampleSizeBox)b, w));
            boxWriters.Add("stsd", (b, w) => WriteSampleDescriptionBox((SampleDescriptionBox)b, w));

            boxWriters.Add("soun", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("mp4a", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("wma ", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("ac-3", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("ec-3", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("mlpa", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("dtsc", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("dtsh", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("dtsl", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("dtse", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("samr", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("sawb", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("drms", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("alac", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("owma", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));
            boxWriters.Add("enca", (b, w) => WriteAudioSampleEntryBox((AudioSampleEntryBox)b, w));

            boxWriters.Add("avc1", (b, w) => WriteVisualSampleEntryBox((VisualSampleEntryBox)b, w));
            boxWriters.Add("s263", (b, w) => WriteVisualSampleEntryBox((VisualSampleEntryBox)b, w));
            boxWriters.Add("mp4v", (b, w) => WriteVisualSampleEntryBox((VisualSampleEntryBox)b, w));
            boxWriters.Add("drmi", (b, w) => WriteVisualSampleEntryBox((VisualSampleEntryBox)b, w));
            boxWriters.Add("encv", (b, w) => WriteVisualSampleEntryBox((VisualSampleEntryBox)b, w));

            boxWriters.Add("esds", (b, w) => WriteElementaryStreamDescriptorBox((ElementaryStreamDescriptorBox)b, w));
            boxWriters.Add("avcC", (b, w) => WriteAdvancedVideoCodingBox((AdvancedVideoCodingBox)b, w));
            boxWriters.Add("moof", (b, w) => WriteMovieFragmentBox((MovieFragmentBox)b, w));
            boxWriters.Add("mfhd", (b, w) => WriteMovieFragmentHeaderBox((MovieFragmentHeaderBox)b, w));
            boxWriters.Add("traf", (b, w) => WriteTrackFragmentBox((TrackFragmentBox)b, w));
            boxWriters.Add("tfra", (b, w) => WriteTrackFragmentRandomAccessBox((TrackFragmentRandomAccessBox)b, w));
            boxWriters.Add("sdtp", (b, w) => WriteIndependentAndDisposableSamplesBox((IndependentAndDisposableSamplesBox)b, w));
            boxWriters.Add("tfhd", (b, w) => WriteTrackFragmentHeaderBox((TrackFragmentHeaderBox)b, w));
            boxWriters.Add("trun", (b, w) => WriteTrackFragmentRunBox((TrackFragmentRunBox)b, w));
            boxWriters.Add("dfxp", (b, w) => WriteDfxpBox((DfxpBox)b, w));
            boxWriters.Add("stpp", (b, w) => WriteSubtitleSampleEntryBox((SubtitleSampleEntryBox)b, w));
            boxWriters.Add("mvex", (b, w) => WriteMovieExtendsBox((MovieExtendsBox)b, w));
            boxWriters.Add("trex", (b, w) => WriteTrackExtendsBox((TrackExtendsBox)b, w));
            boxWriters.Add("mdat", (b, w) => WriteMediaDataBox((MediaDataBox)b, w));
            boxWriters.Add("mfra", (b, w) => WriteMovieFragmentRandomAccessBox((MovieFragmentRandomAccessBox)b, w));
            boxWriters.Add("sidx", (b, w) => WriteSegmentIndexBox((SegmentIndexBox)b, w));
            boxWriters.Add("pssh", (b, w) => WriteProtectionSystemSpecificHeaderBox((ProtectionSystemSpecificHeaderBox)b, w));
            boxWriters.Add("sinf", (b, w) => WriteProtectedSchemeInformationBox((ProtectedSchemeInformationBox)b, w));
            boxWriters.Add("schi", (b, w) => WriteSchemeInformationBox((SchemeInformationBox)b, w));
            boxWriters.Add("free", (b, w) => WriteFreeSpaceBox((FreeSpaceBox)b, w));
            boxWriters.Add("frma", (b, w) => WriteFormatBox((FormatBox)b, w));
            boxWriters.Add("schm", (b, w) => WriteSchemeTypeBox((SchemeTypeBox)b, w));
            boxWriters.Add("mehd", (b, w) => WriteMovieExtendsHeaderBox((MovieExtendsHeaderBox)b, w));
            boxWriters.Add("tfdt", (b, w) => WriteTrackFragmentBaseMediaDecodeTimeBox((TrackFragmentBaseMediaDecodeTimeBox)b, w));
            boxWriters.Add("tenc", (b, w) => WriteTrackEncryptionBox((TrackEncryptionBox)b, w));
            boxWriters.Add("ainf", (b, w) => WriteAssetInformationBox((AssetInformationBox)b, w));
        }

#if NETFX_CORE
        public static void WriteAllBoxes(IList<IBox> boxes, IOutputStream outputStream)
        {
            var stream = outputStream.AsStreamForWrite(); // don't dispose, the caller may want this stream still
            using (var writer = new BoxBinaryWriter(stream, true))
            {
                foreach (var box in boxes)
                {
                    WriteBox(box, writer);
                }
                writer.Flush();
            }
            stream.Flush();
        }

        public static void WriteBox(IBox box, IOutputStream outputStream)
        {
            using (var writer = new BoxBinaryWriter(outputStream.AsStreamForWrite(), true))
            {
                MP4BoxWriter.WriteBox(box, writer);
                writer.Flush();
            }
        }
#else
        public static void WriteAllBoxes(IList<IBox> boxes, Stream outputStream)
        {
            var writer = new BoxBinaryWriter(outputStream);
            {
                foreach (var box in boxes)
                {
                    WriteBox(box, writer);
                }
                writer.Flush();
            }
            outputStream.Flush();
        }

        public static void WriteBox(IBox box, Stream outputStream)
        {
            var writer = new BoxBinaryWriter(outputStream);
            {
                MP4BoxWriter.WriteBox(box, writer);
                writer.Flush();
            }
        }
#endif

        internal static void WriteBox(IBox box, BoxBinaryWriter writer)
        {
            using (var outputStream = new MemoryStream())
            {
#if NETFX_CORE
                using (var boxWriter = new BoxBinaryWriter(outputStream, true))
#else
                var boxWriter = new BoxBinaryWriter(outputStream);
#endif
                {
                    if (box is IFullBox && !(box is IUniversalUniqueIdentifierBox)) WriteFullBoxHeader((IFullBox)box, boxWriter);
                    Action<IBox, BoxBinaryWriter> boxContentsWriter;
                    if (boxWriters.TryGetValue(box.Type, out boxContentsWriter))
                    {
                        boxContentsWriter(box, boxWriter);
                    }
                    else if (box is UnknownBox)
                    {
                        WriteUnknownBox((UnknownBox)box, boxWriter);
                    }
                    else
                    {
                        throw new NotImplementedException();
                    }

                    if (box is IContainerBox) WriteInnerBoxes((IContainerBox)box, boxWriter);
                    boxWriter.Flush();
                }
                var size = (ulong)outputStream.Length;

                WriteBoxHeader(box.Type, size, writer);

                writer.Write(outputStream.ToArray());
            }
        }

        static void WriteBoxHeader(string boxType, ulong size, BoxBinaryWriter writer)
        {
            if (boxType.Length != 4) throw new ArgumentOutOfRangeException("boxType");

            size += BASEBOXSIZE;
            if (size > uint.MaxValue) // large box
            {
                size += LARGESIZEADD;
                writer.Write((uint)1);
                writer.Write(boxType);
                writer.Write(size);
            }
            else // small box
            {
                writer.Write((uint)size);
                writer.Write(boxType);
            }
        }

        static void WriteFullBoxHeader(IFullBox fullBox, BoxBinaryWriter writer)
        {
            writer.Write((uint)((fullBox.Version << 0x18) + fullBox.Flags));
        }

        static void WriteInnerBoxes(IContainerBox containerBox, BoxBinaryWriter writer)
        {
            foreach (var box in containerBox.InnerBoxes)
            {
                WriteBox(box, writer);
            }
        }

        static void WriteFileTypeBox(FileTypeBox box, BoxBinaryWriter writer)
        {
            if (box.MajorBrand.Length != 4) throw new ArgumentOutOfRangeException("box", "MajorBrand length must be 4");
            if (box.CompatibleBrands.Any(b => b.Length != 4)) throw new ArgumentOutOfRangeException("box", "All elements of CompatibleBrands must have a length of 4");

            writer.Write(box.MajorBrand);
            writer.Write(box.MinorVersion);
            foreach (var cb in box.CompatibleBrands)
            {
                writer.Write(cb);
            }
        }

        static void WriteUniversalUniqueIdentifierBox(IUniversalUniqueIdentifierBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.Uuid);
            if (box is IFullBox)
            {
                WriteFullBoxHeader((IFullBox)box, writer);
            }

            Action<IUniversalUniqueIdentifierBox, BoxBinaryWriter> extendedBoxWriter;
            if (extendedBoxWriters.TryGetValue(box.Uuid, out extendedBoxWriter))
            {
                extendedBoxWriter(box, writer);
            }
            else
            {
                var uuidBox = box as UniversalUniqueIdentifierBox;
                if (uuidBox != null && uuidBox.Data != null) writer.Write(uuidBox.Data);
            }
        }

        static void WriteTrackFragmentExtendedHeaderBox(TrackFragmentExtendedHeaderBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.FraqmentAbsoluteTime);
            writer.Write(box.FragmentDuration);
        }

        static void WriteLiveServerManifestBox(LiveServerManifestBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.Manifest);
        }

        static void WriteMovieBox(MovieBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteMovieHeaderBox(MovieHeaderBox box, BoxBinaryWriter writer)
        {
            if (box.Matrix.Length != 9) throw new ArgumentOutOfRangeException("box", "Maxrix length must be 9");

            if (box.Version == 0)
            {
                writer.WriteUInt32(box.CreationTime);
                writer.WriteUInt32(box.ModificationTime);
                writer.Write(box.TimeScale);
                writer.Write((uint)box.Duration);
            }
            else
            {
                writer.Write(box.CreationTime);
                writer.Write(box.ModificationTime);
                writer.Write(box.TimeScale);
                writer.Write(box.Duration);
            }
            writer.Write(box.Rate);
            writer.Write(box.Volume);
            writer.WriteBytes(0, 2); // reserved 1
            writer.WriteBytes(0, 8); // reserved 2
            for (int i = 0; i < 9; i++)
            {
                writer.Write(box.Matrix[i]);
            }
            writer.WriteBytes(0, 24); // predefined
            writer.Write(box.NextTrackId);
        }

        static void WriteTrackBox(TrackBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }
        static void WriteTrackHeaderBox(TrackHeaderBox box, BoxBinaryWriter writer)
        {
            if (box.Matrix.Length != 9) throw new ArgumentOutOfRangeException("box", "Maxrix length must be 9");

            if (box.Version == 0)
            {
                writer.WriteUInt32(box.CreationTime);
                writer.WriteUInt32(box.ModificationTime);
                writer.Write(box.TrackId);
                writer.Write((uint)0); // reserved 1
                writer.Write((uint)box.Duration);
            }
            else
            {
                writer.Write(box.CreationTime);
                writer.Write(box.ModificationTime);
                writer.Write(box.TrackId);
                writer.Write((uint)0); // reserved 1
                writer.Write(box.Duration);
            }
            writer.WriteBytes(0, 8); // reserved 2
            writer.Write(box.Layer);
            writer.Write(box.AlternateGroup);
            writer.Write(box.Volume);
            writer.Write((ushort)0); // reserved 3
            for (int i = 0; i < 9; i++)
            {
                writer.Write(box.Matrix[i]);
            }
            writer.Write(box.Width);
            writer.Write(box.Height);
        }

        static void WriteMediaBox(MediaBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteMediaHeaderBox(MediaHeaderBox box, BoxBinaryWriter writer)
        {
            if (box.Version == 0)
            {
                writer.WriteUInt32(box.CreationTime);
                writer.WriteUInt32(box.ModificationTime);
                writer.Write(box.Timescale);
                writer.Write((uint)box.Duration);
            }
            else
            {
                writer.Write(box.CreationTime);
                writer.Write(box.ModificationTime);
                writer.Write(box.Timescale);
                writer.Write(box.Duration);
            }
            writer.WriteToUSort(box.Language);
            writer.Write(box.Quality);
        }

        static void WriteHandlerReferenceBox(HandlerReferenceBox box, BoxBinaryWriter writer)
        {
            if (box.HandlerType.Length != 4) throw new ArgumentOutOfRangeException("box", "HandlerType length must be 4");

            writer.Write((uint)0); // predefined
            writer.Write(box.HandlerType);
            writer.Write((uint)0); // reserved 0
            writer.Write((uint)0); // reserved 1
            writer.Write((uint)0); // reserved 2
            if (box.Name != null)
                writer.WriteNullTerminatedString(box.Name);
        }

        static void WriteMediaInformationBox(MediaInformationBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteSyncSamplesBox(SyncSamplesBox box, BoxBinaryWriter writer)
        {
            writer.Write((uint)box.SyncTable.Count);
            foreach (var item in box.SyncTable)
            {
                writer.Write(item);
            }
        }

        static void WriteSoundMediaHeaderBox(SoundMediaHeaderBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.Balance);
        }

        static void WriteVideoMediaHeaderBox(VideoMediaHeaderBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.GraphicsMode);
            writer.Write(box.Color[0]);
            writer.Write(box.Color[1]);
            writer.Write(box.Color[2]);
        }

        static void WriteNullMediaHeaderBox(NullMediaHeaderBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteUserDataBox(UserDataBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteMetaBox(MetaBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteItemListBox(ItemListBox box, BoxBinaryWriter writer)
        {
            if (box.Data != null)
            {
                writer.Write(box.Data);
            }
        }

        static void WriteXtraBox(XtraBox box, BoxBinaryWriter writer)
        {
            if (box.Data != null)
            {
                writer.Write(box.Data);
            }
        }

        static void WriteDataInformationBox(DataInformationBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteDataReferenceBox(DataReferenceBox box, BoxBinaryWriter writer)
        {
            writer.Write((uint)box.InnerBoxes.Count);
        }

        static void WriteUrlBox(UrlBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteSampleTableBox(SampleTableBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteDecodingTimeToSampleBox(DecodingTimeToSampleBox box, BoxBinaryWriter writer)
        {
            writer.Write((uint)box.Entries.Count);
            foreach (var entry in box.Entries)
            {
                writer.Write(entry.SampleCount);
                writer.Write(entry.SampleDelta);
            }
        }

        static void WriteCompositionTimeToSampleBox(CompositionTimeToSampleBox box, BoxBinaryWriter writer)
        {
            writer.Write((uint)box.Entries.Count);
            foreach (var entry in box.Entries)
            {
                writer.Write(entry.SampleCount);
                writer.Write(entry.SampleOffset);
            }
        }

        static void WriteSampleChunkBox(SampleChunkBox box, BoxBinaryWriter writer)
        {
            writer.Write((uint)box.Entries.Count);
            foreach (var entry in box.Entries)
            {
                writer.Write(entry.FirstChunk);
                writer.Write(entry.SamplesPerChunk);
                writer.Write(entry.SampleDescIndex);
            }
        }

        static void WriteSampleChunkOffsetBox(SampleChunkOffsetBox box, BoxBinaryWriter writer)
        {
            writer.Write((uint)box.Offsets.Count);
            foreach (var entry in box.Offsets)
            {
                writer.Write(entry);
            }
        }

        static void WriteSampleSizeBox(SampleSizeBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.ConstantSize);
            writer.Write((uint)box.SizeTable.Count);
            if (box.ConstantSize == 0)
            {
                foreach (var entry in box.SizeTable)
                {
                    writer.Write(entry);
                }
            }
        }

        static void WriteMovieExtendsBox(MovieExtendsBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteTrackExtendsBox(TrackExtendsBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.TrackId);
            writer.Write(box.SampleDescriptionIndex);
            writer.Write(box.SampleDuration);
            writer.Write(box.SampleSize);
            writer.Write(box.SampleFlags.GetValue());
        }

        static void WriteMovieFragmentRandomAccessBox(MovieFragmentRandomAccessBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteSegmentIndexBox(SegmentIndexBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.ReferenceId);
            writer.Write(box.Timescale);

            if (box.Version == 1)
            {
                writer.Write(box.EarliestPresentationTime);
                writer.Write(box.FirstOffset);
            }
            else
            {
                writer.Write((uint)box.EarliestPresentationTime);
                writer.Write((uint)box.FirstOffset);
            }

            writer.Write((ushort)0);
            writer.Write((ushort)box.Subsegments.Count);

            foreach (var subsegment in box.Subsegments)
            {
                uint referenceNum = subsegment.ReferencedSize;
                if (subsegment.ReferenceType) referenceNum += 0x80000000;
                writer.Write(referenceNum);

                writer.Write(subsegment.Duration);

                uint sapNum = subsegment.SAPDeltaTime;
                sapNum += ((uint)subsegment.SAPType << 28);
                if (subsegment.StartsWithSAP) sapNum += 0x80000000;
                writer.Write(sapNum);
            }
        }

        static void WriteProtectionSystemSpecificHeaderBox(ProtectionSystemSpecificHeaderBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.SystemId);
            writer.Write(box.Data.Length);
            writer.Write(box.Data);
        }

        static void WriteProtectedSchemeInformationBox(ProtectedSchemeInformationBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteSchemeInformationBox(SchemeInformationBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteFreeSpaceBox(FreeSpaceBox box, BoxBinaryWriter writer)
        {
            writer.WriteBytes(0, box.Size);
        }

        static void WriteFormatBox(FormatBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.Format);
        }

        static void WriteSchemeTypeBox(SchemeTypeBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.SchemeType);
            writer.Write(box.SchemeVersion);
            if ((box.Flags & 0x01) == 0x01)
            {
                writer.WriteNullTerminatedString(box.SchemeUri);
            }
        }

        static void WriteMovieExtendsHeaderBox(MovieExtendsHeaderBox box, BoxBinaryWriter writer)
        {
            if ((box.Version & 0x01) == 0x01)
            {
                writer.Write(box.FragmentDuration);
            }
            else
            {
                writer.Write((uint)box.FragmentDuration);
            }
        }

        static void WriteTrackFragmentBaseMediaDecodeTimeBox(TrackFragmentBaseMediaDecodeTimeBox box, BoxBinaryWriter writer)
        {
            if ((box.Version & 0x01) == 0x01)
            {
                writer.Write(box.BaseMediaDecodeTime);
            }
            else
            {
                writer.Write((uint)box.BaseMediaDecodeTime);
            }
        }

        static void WriteTrackEncryptionBox(TrackEncryptionBox box, BoxBinaryWriter writer)
        {
            writer.WriteUInt24(box.DefaultIsEntrypted);
            writer.Write(box.DefaultIVSize);
            writer.Write(box.DefaultKID);
        }

        static void WriteAssetInformationBox(AssetInformationBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.ProfileVersion);
            if (box.Apid != null)
            {
                writer.WriteNullTerminatedString(box.Apid);
            }
        }

        static void WriteTrackFragmentRandomAccessBox(TrackFragmentRandomAccessBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.TrackId);
            writer.Write((box.LengthSizeOfTrafNum << 4) + (box.LengthSizeOfTrunNum << 4) + (box.LengthSizeOfSampleNum));
            writer.Write(box.TrackFragmentRandomAccessEntries.Count);
            foreach (var entry in box.TrackFragmentRandomAccessEntries)
            {
                if (box.Version == 1)
                {
                    writer.Write(entry.Time);
                    writer.Write(entry.MoofOffset);
                }
                else
                {
                    writer.Write((uint)entry.Time);
                    writer.Write((uint)entry.MoofOffset);
                }

                writer.WriteIntValueInBytes(entry.TrafNumber, box.LengthSizeOfTrafNum);
                writer.WriteIntValueInBytes(entry.TrunNumber, box.LengthSizeOfTrunNum);
                writer.WriteIntValueInBytes(entry.SampleNumber, box.LengthSizeOfSampleNum);
            }
        }

        static void WriteSampleDescriptionBox(SampleDescriptionBox box, BoxBinaryWriter writer)
        {
            writer.Write((uint)box.SampleEntries.Count);
        }

        static void WriteDfxpBox(DfxpBox box, BoxBinaryWriter writer)
        {
            writer.WriteBytes(0, 6); // reserved
            writer.Write(box.DataReferenceIndex);
        }

        static void WriteSubtitleSampleEntryBox(SubtitleSampleEntryBox box, BoxBinaryWriter writer)
        {
            writer.WriteBytes(0, 6); // reserved
            writer.Write(box.DataReferenceIndex);
            writer.WriteNullTerminatedString(box.Namespace);
            writer.WriteNullTerminatedString(box.SchemaLocation);
            writer.WriteNullTerminatedString(box.ImageMimeType);
        }

        static void WriteAudioSampleEntryBox(AudioSampleEntryBox box, BoxBinaryWriter writer)
        {
            writer.WriteBytes(0, 6); // reserved
            writer.Write(box.DataReferenceIndex);
            writer.Write(box.AudioEncodingVersion);
            writer.Write(box.AudioEncodingRevisionLevel);
            writer.Write(box.AudioEncodingVendor);
            writer.Write(box.ChannelCount);
            writer.Write(box.SampleSize);
            writer.Write(box.AudioCompressionId);
            writer.Write(box.AudioPacketSize);
            writer.Write(box.SampleRate);
        }

        static void WriteVisualSampleEntryBox(VisualSampleEntryBox box, BoxBinaryWriter writer)
        {
            writer.WriteBytes(0, 6); // reserved
            writer.Write(box.DataReferenceIndex);
            writer.Write((ushort)0); // predefined 1
            writer.Write((ushort)0); // reserved 1
            writer.WriteBytes(0, 12); // predefined 2
            writer.Write(box.Width);
            writer.Write(box.Height);
            writer.Write(box.HorizResolution);
            writer.Write(box.VertResolution);
            writer.Write((uint)0); // reserved 2
            writer.Write(box.FrameCount);
            writer.Write((byte)box.CompressorName.Length);
            writer.Write(box.CompressorName);
            writer.WriteBytes(0, (uint)(31 - box.CompressorName.Length));

            writer.Write(box.Depth);
            writer.Write((short)-1); // predefined as -1
        }

        static void WriteAdvancedVideoCodingBox(AdvancedVideoCodingBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.ConfigurationVersion);
            writer.Write(box.AvcProfileIndication);
            writer.Write(box.AvcCompatibleProfiles);
            writer.Write(box.AvcLevelIndication);
            writer.Write((byte)((0x3f << 2) + (box.NaluLengthSize - 1))); // 6 bits reserved (111111), last 2 bits contain NALU length size minus one

            writer.Write((byte)((0x07 << 5) + box.SequenceParameters.Count)); // 3 bits reserved (111), last 5 bits contain SPS count
            foreach (var sp in box.SequenceParameters)
            {
                writer.Write((ushort)sp.Count);
                writer.Write(sp.ToArray());
            }

            writer.Write((byte)box.PictureParameters.Count);
            foreach (var pp in box.PictureParameters)
            {
                writer.Write((ushort)pp.Count);
                writer.Write(pp.ToArray());
            }
        }

        static void WriteElementaryStreamDescriptorBox(ElementaryStreamDescriptorBox box, BoxBinaryWriter writer)
        {
            box.StreamDescriptor.Write(writer);
        }

        static void Write(this IDescriptor descriptor, BoxBinaryWriter writer)
        {
            var offset = writer.BaseStream.Position;

            writer.Write((byte)descriptor.Tag);

            for (int i = 1; i < descriptor.HeaderSize - 1; i++)
            {
                writer.Write((byte)0x80);
            }
            writer.Write((byte)descriptor.PayloadSize);

            if (descriptor is ElementaryStreamDescriptor)
                ((ElementaryStreamDescriptor)descriptor).Write(writer);
            else if (descriptor is DecoderConfigurationDescriptor)
                ((DecoderConfigurationDescriptor)descriptor).Write(writer);
            else if (descriptor is DecoderSpecificInformationDescriptor)
                ((DecoderSpecificInformationDescriptor)descriptor).Write(writer);
            else if (descriptor is SyncronizationConfigurationDescriptor)
                ((SyncronizationConfigurationDescriptor)descriptor).Write(writer);

            foreach (var subDescriptor in descriptor.SubDescriptors)
            {
                subDescriptor.Write(writer);
            }
        }

        static void Write(this ElementaryStreamDescriptor descriptor, BoxBinaryWriter writer)
        {
            writer.Write(descriptor.ElementaryStreamId);
            writer.Write((byte)((descriptor.Flags << 0x7) + descriptor.StreamPriority));

            if ((descriptor.Flags & 1) != 0)
            {
                writer.Write(descriptor.DependsOn);
            }

            if ((descriptor.Flags & 2) != 0)
            {
                writer.Write((byte)descriptor.Url.Length);
                writer.Write(descriptor.Url);
            }

            if ((descriptor.Flags & 2) != 0)
            {
                writer.Write(descriptor.OcrElementaryStreamId);
            }
        }

        static void Write(this DecoderConfigurationDescriptor descriptor, BoxBinaryWriter writer)
        {
            writer.Write((byte)descriptor.ObjectTypeIndication);
            writer.Write((byte)((descriptor.StreamType << 2) + (descriptor.UpStream ? 2 : 1)));
            writer.WriteUInt24(descriptor.BufferSize);
            writer.Write(descriptor.MaxBitrate);
            writer.Write(descriptor.AverageBitrate);
        }

        static void Write(this DecoderSpecificInformationDescriptor descriptor, BoxBinaryWriter writer)
        {
            writer.Write(descriptor.Information);
        }

        static void Write(this SyncronizationConfigurationDescriptor descriptor, BoxBinaryWriter writer)
        {
            writer.Write(descriptor.Predefined);
        }

        static void WriteMovieFragmentBox(MovieFragmentBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteMovieFragmentHeaderBox(MovieFragmentHeaderBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.SequenceNumber);
        }

        static void WriteTrackFragmentBox(TrackFragmentBox box, BoxBinaryWriter writer)
        {
            // nothing to write
        }

        static void WriteTrackFragmentHeaderBox(TrackFragmentHeaderBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.TrackId);
            if ((box.Flags & 1) != 0)
            {
                writer.Write(box.BaseDataOffset);
            }
            if ((box.Flags & 2) != 0)
            {
                writer.Write(box.SampleDescriptionIndex);
            }
            if ((box.Flags & 8) != 0)
            {
                writer.Write(box.DefaultSampleDuration);
            }
            if ((box.Flags & 0x10) != 0)
            {
                writer.Write(box.DefaultSampleSize);
            }
            if ((box.Flags & 0x20) != 0)
            {
                writer.Write(box.DefaultSampleFlags.GetValue());
            }
        }

        static void WriteTrackFragmentRunBox(TrackFragmentRunBox box, BoxBinaryWriter writer)
        {
            writer.Write((uint)box.Samples.Count);

            if ((box.Flags & 1) != 0)
            {
                writer.Write(box.DataOffset);
            }

            if ((box.Flags & 4) != 0)
            {
                writer.Write(box.FirstSampleFlags.GetValue());
            }

            foreach (var sample in box.Samples)
            {
                if ((box.Flags & 0x100) != 0)
                {
                    writer.Write(sample.SampleDuration);
                }
                if ((box.Flags & 0x200) != 0)
                {
                    writer.Write(sample.SampleSize);
                }
                if ((box.Flags & 0x400) != 0)
                {
                    writer.Write(sample.SampleFlags.GetValue());
                }
                if ((box.Flags & 0x800) != 0)
                {
                    writer.Write(sample.SampleCompositionTimeOffset);
                }
            }
        }

        static void WriteIndependentAndDisposableSamplesBox(IndependentAndDisposableSamplesBox box, BoxBinaryWriter writer)
        {
            foreach (var sample in box.Samples)
            {
                int b = 0;
                b += sample.SampleDependsOn << 4;
                b += sample.SampleIsDependedOn << 2;
                b += sample.SampleHasRedundancy;
                writer.Write((byte)b);
            }
        }

        static void WriteMediaDataBox(MediaDataBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.Data);
        }

        static void WriteUnknownBox(UnknownBox box, BoxBinaryWriter writer)
        {
            writer.Write(box.Data);
        }
    }
}
